﻿using System;
using System.Linq;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using System.Xml.Serialization;
using System.Text.RegularExpressions;
using System.Web.UI.HtmlControls;
using Microsoft.SharePoint.WebPartPages;
using Microsoft.SharePoint.Utilities;
using System.Drawing;

namespace CalendarWebpartSandboxed.CalendarWebpart
{
    [XmlRoot(Namespace = "http://adrisen.com/WebPart/v1/CalendarWebpart")]
    public class CalendarWebpart : System.Web.UI.WebControls.WebParts.WebPart
    {
        private const string WZ_SCRIPT_KEY = "wztooltipscript";
        private Regex tagRegex = new Regex("<(.|\n)*?>");

        private Calendar cal = new Calendar();
        private Label lblExceptions = new Label();
        private HyperLink lnkAddNewItem = new HyperLink();
        private LinkButton lnkExport = new LinkButton();
        private HtmlGenericControl divTooltip = new HtmlGenericControl("div");
        private SPWeb calendarWeb = null;

        private string listName;
        private string dateField;
        private string endDateField;
        private string dayForeColor;
        private string titleForeColor;
        private string titleBackColor;
        private string tooltipFieldID;
        private bool showAddNew = true;
        private bool showExport = true;
        private string tooltipArray = string.Empty;
        private int tooltipIndex = 0;
        private string tooltipClass = string.Empty;

        private SPListItemCollection events;

        [Personalizable(PersonalizationScope.Shared), 
        FriendlyName("The web Id"),
        Category("Binding"),
        Browsable(false)]
        public string WebId { get; set; }

        [Personalizable(PersonalizationScope.Shared),
        FriendlyName("The list name"),
        Category("Binding"),
        Browsable(false)]
        public string ListName
        {
            get { return listName; }
            set { listName = value; }
        }

        [Personalizable(PersonalizationScope.Shared),
        FriendlyName("The date field"),
        Category("Binding"),
        Browsable(false)]
        public string DateField
        {
            get { return dateField; }
            set { dateField = value; }
        }

        [Personalizable(PersonalizationScope.Shared),
        FriendlyName("The end date field"),
        Category("Binding"),
        Browsable(false)]
        public string EndDateField
        {
            get { return endDateField; }
            set { endDateField = value; }
        }

        [Personalizable(PersonalizationScope.Shared),
        FriendlyName("The tooltip field"),
        Category("Binding"),
        Browsable(false)]
        public string TooltipField
        {
            get { return tooltipFieldID; }
            set { tooltipFieldID = value; }
        }

        [Personalizable(PersonalizationScope.Shared),
        FriendlyName("The day foreground color"),
        Category("Design"),
        Browsable(true)]
        public string DayForeColor
        {
            get { return dayForeColor; }
            set { dayForeColor = value; }
        }

        [Personalizable(PersonalizationScope.Shared),
        FriendlyName("The day background color"),
        Category("Design"),
        Browsable(true)]
        public string DayBackColor { get; set; }

        [Personalizable(PersonalizationScope.Shared),
        FriendlyName("The holiday background color"),
        Category("Design"),
        Browsable(true)]
        public string HolidayBackColor { get; set; }

        [Personalizable(PersonalizationScope.Shared),
        FriendlyName("The other month background color"),
        Category("Design"),
        Browsable(true)]
        public string OtherMonthBackColor { get; set; }

        [Personalizable(PersonalizationScope.Shared),
        FriendlyName("The title foreground color"),
        Category("Design"),
        Browsable(true)]
        public string TitleForeColor
        {
            get { return titleForeColor; }
            set { titleForeColor = value; }
        }

        [Personalizable(PersonalizationScope.Shared),
        FriendlyName("The title background color"),
        Category("Design"),
        Browsable(true)]
        public string TitleBackColor
        {
            get { return titleBackColor; }
            set { titleBackColor = value; }
        }

        [Personalizable(PersonalizationScope.Shared),
        FriendlyName("Show the Add New link"),
        Category("Design"),
        Browsable(true),
        DefaultValue(true)]
        public bool ShowAddNew
        {
            get { return showAddNew; }
            set { showAddNew = value; }
        }

        [Personalizable(PersonalizationScope.Shared),
        FriendlyName("Show the Export iCal link"),
        Category("Design"),
        Browsable(true),
        DefaultValue(true)]
        public bool ShowExport
        {
            get { return showExport; }
            set { showExport = value; }
        }

        [Personalizable(PersonalizationScope.Shared),
        FriendlyName("Tooltip CSS class"),
        Category("Design"),
        Browsable(true),
        DefaultValue("")]
        public string TooltipClass
        {
            get { return tooltipClass; }
            set { tooltipClass = value; }
        }

        [Personalizable(PersonalizationScope.Shared),
        FriendlyName("Holiday dates (comma separated dates)"),
        Category("Design"),
        Browsable(true)]
        public string HolidayDates { get; set; }

        [Personalizable(PersonalizationScope.Shared),
        FriendlyName("Show alternate calendar"),
        Category("Design"),
        Browsable(true)]
        public bool ShowAlternateCalendar { get; set; }

        [Personalizable(PersonalizationScope.Shared),
        FriendlyName("Filter events by view"),
        Category("Binding"),
        Browsable(false)]
        public string FilterView { get; set; }

        private SPWeb CalendarWeb
        {
            get
            {
                if (calendarWeb == null && !string.IsNullOrEmpty(WebId))
                {
                    calendarWeb = SPContext.Current.Site.OpenWeb(new Guid(WebId));
                }
                return calendarWeb ?? SPContext.Current.Web;
            }
        }

        public CalendarWebpart()
        {
            cal.DataBinding += new EventHandler(cal_DataBinding);
            cal.VisibleMonthChanged += new MonthChangedEventHandler(cal_VisibleMonthChanged);
            cal.DayRender += new DayRenderEventHandler(cal_DayRender);
            cal.SelectionChanged += new EventHandler(cal_SelectionChanged);
            SPRegionalSettings regionalSettings = CalendarWeb.CurrentUser.RegionalSettings ?? CalendarWeb.RegionalSettings;
            cal.FirstDayOfWeek = (FirstDayOfWeek)regionalSettings.FirstDayOfWeek;
        }

        protected override void OnUnload(EventArgs e)
        {
            base.OnUnload(e);

            if (calendarWeb != null && calendarWeb.ID != SPContext.Current.Web.ID)
            {
                calendarWeb.Dispose();
                calendarWeb = null;
            }
            events = null;
        }

        void cal_VisibleMonthChanged(object sender, MonthChangedEventArgs e)
        {
            if (!string.IsNullOrEmpty(WebId) && !string.IsNullOrEmpty(listName))
            {
                SPQuery query = new SPQuery();
                query.CalendarDate = cal.VisibleDate;
                query.ExpandRecurrence = true;
                query.Query = "<Where>" +
                    "<DateRangesOverlap>" +
                        "<FieldRef Name=\"" + dateField + "\"></FieldRef>" +
                        "<FieldRef Name=\"" + endDateField + "\"></FieldRef>" +
                        "<FieldRef Name=\"RecurrenceID\"></FieldRef>" +
                        "<Value Type=\"DateTime\"><Month/></Value>" +
                    "</DateRangesOverlap></Where>";
                query.ViewFields = "<FieldRef Name=\"" + dateField + "\" />" +
                                   "<FieldRef Name=\"" + endDateField + "\" />" +
                                   "<FieldRef Name=\"RecurrenceID\" />" +
                                   "<FieldRef Name=\"" + tooltipFieldID + "\" />";
                if (string.IsNullOrEmpty(FilterView))
                {
                    events = CalendarWeb.Lists[listName].GetItems(query);
                }
                else
                {
                    events = CalendarWeb.Lists[listName].GetItems(query, FilterView);
                }
            }
        }

        void cal_DataBinding(object sender, EventArgs e)
        {
            if (!string.IsNullOrEmpty(WebId) && !string.IsNullOrEmpty(listName))
            {
                SPQuery query = new SPQuery();
                query.CalendarDate = cal.VisibleDate;
                query.ExpandRecurrence = true;
                query.Query = "<Where>" +
                    "<DateRangesOverlap>" +
                        "<FieldRef Name=\"" + dateField + "\"></FieldRef>" +
                        "<FieldRef Name=\"" + endDateField + "\"></FieldRef>" +
                        "<FieldRef Name=\"RecurrenceID\"></FieldRef>" +
                        "<Value Type=\"DateTime\"><Month/></Value>" +
                    "</DateRangesOverlap></Where>";
                query.ViewFields = "<FieldRef Name=\"" + dateField + "\" />" +
                                   "<FieldRef Name=\"" + endDateField + "\" />" +
                                   "<FieldRef Name=\"RecurrenceID\" />" +
                                   "<FieldRef Name=\"" + tooltipFieldID + "\" />";
                if (string.IsNullOrEmpty(FilterView))
                {
                    events = CalendarWeb.Lists[listName].GetItems(query);
                }
                else
                {
                    events = CalendarWeb.Lists[listName].GetItems(query, FilterView);
                }
            }
        }

        void cal_SelectionChanged(object sender, EventArgs e)
        {
            try
            {
                if (!string.IsNullOrEmpty(WebId) && !string.IsNullOrEmpty(listName) && !string.IsNullOrEmpty(dateField))
                {
                    var items = (from SPListItem evt in events
                                 where ((DateTime)evt[dateField]).Date == cal.SelectedDate.Date
                                 select evt).First();

                    HttpContext.Current.Response.Redirect(CalendarWeb.Url + "/" + CalendarWeb.Lists[listName].DefaultDisplayFormUrl + "?ID=" + items.ID, false);
                }
            }
            catch (Exception ex)
            {
                lblExceptions.Text += ex.ToString();
            }
        }

        void cal_DayRender(object sender, DayRenderEventArgs e)
        {
            try
            {
                if (!string.IsNullOrEmpty(dayForeColor))
                {
                    e.Cell.ForeColor = System.Drawing.Color.FromName(dayForeColor);
                }
                e.Cell.Style["background-color"] = e.Day.IsOtherMonth ? OtherMonthBackColor : IsHoliday(e) ? HolidayBackColor : DayBackColor;

                if (e.Day.IsToday)
                {
                    e.Cell.CssClass = "asn-calendartitle";
                }
                if (!string.IsNullOrEmpty(WebId) && !string.IsNullOrEmpty(listName) && !string.IsNullOrEmpty(dateField))
                {
                    SPRegionalSettings regionalSettings = CalendarWeb.CurrentUser.RegionalSettings ?? CalendarWeb.RegionalSettings;
                    System.Globalization.Calendar primarycalendar = getAlternateCalendar(regionalSettings.CalendarType);
                    System.Globalization.Calendar altCalendar = ShowAlternateCalendar ? getAlternateCalendar(regionalSettings.AlternateCalendarType) : null;

                    string listUrl = SPUrlUtility.CombineUrl(CalendarWeb.ServerRelativeUrl, CalendarWeb.Lists[listName].DefaultDisplayFormUrl);

                    var items = from SPListItem evt in events
                                where ((DateTime)evt[dateField]).Date <= e.Day.Date && ((DateTime)evt[endDateField]).Date >= e.Day.Date
                                select evt;
                    if (items.Count<SPListItem>() > 0)
                    {
                        if (string.IsNullOrEmpty(tooltipArray))
                        {
                            tooltipArray += "\"";
                        }
                        else
                        {
                            tooltipArray += ",\"";
                        }
                        foreach (SPListItem item in items)
                        {
                            string snipet;
                            if (string.IsNullOrEmpty(tooltipFieldID))
                            {
                                snipet = item.Title;
                            }
                            else
                            {
                                snipet = tagRegex.Replace(Convert.ToString(item[tooltipFieldID]), string.Empty);
                            }
                            tooltipArray += string.Format("<div noWrap class='{2}'><a onclick='SP.UI.ModalDialog.ShowPopupDialog(\\\"{0}\\\");return false;' href='{0}'>{1}</a></div> ", listUrl + "?ID=" + item.ID + "&Source=" + Page.Request.RawUrl,
                                snipet.Replace("\"", "&dquo;"), tooltipClass);
                        }
                        tooltipArray += "\"";
                        e.Cell.Text = string.Format("<a onclick='SP.UI.ModalDialog.ShowPopupDialog(\"{0}\");return false;' style='font-weight:bold' onmouseover='CWPShowTooltip(" + tooltipIndex + ")' onmouseout='CWPHideTooltip()'" +
                            "href='{0}'>", listUrl + "?ID=" + items.First<SPListItem>().ID + "&Source=" + Page.Request.RawUrl) +
                            primarycalendar.GetDayOfMonth(e.Day.Date).ToString() +
                            (altCalendar != null ? "<br/>(" + altCalendar.GetDayOfMonth(e.Day.Date) + ")" : "") +
                            "</a>";
                        tooltipIndex++;

                    }
                    else
                    {
                        e.Cell.Text = primarycalendar.GetDayOfMonth(e.Day.Date).ToString() + (altCalendar != null ? "<br/>(" + altCalendar.GetDayOfMonth(e.Day.Date) + ")" : "");
                    }
                }
            }
            catch (Exception ex)
            {
                lblExceptions.Text += ex.ToString();
            }
        }

        private System.Globalization.Calendar getAlternateCalendar(short calendarType)
        {
            switch (calendarType)
            {
                case 0:
                    return null;
                case 1:
                    return new System.Globalization.GregorianCalendar();
                case 3:
                    return new System.Globalization.JapaneseCalendar();
                case 5:
                    return new System.Globalization.KoreanCalendar();
                case 6:
                    return new System.Globalization.HijriCalendar();
                case 7:
                    return new System.Globalization.ThaiBuddhistCalendar();
                case 8:
                    return new System.Globalization.HebrewCalendar();
                case 9:
                    return new System.Globalization.GregorianCalendar(System.Globalization.GregorianCalendarTypes.MiddleEastFrench);
                case 10:
                    return new System.Globalization.GregorianCalendar(System.Globalization.GregorianCalendarTypes.Arabic);
                case 11:
                    return new System.Globalization.GregorianCalendar(System.Globalization.GregorianCalendarTypes.TransliteratedEnglish);
                case 12:
                    return new System.Globalization.GregorianCalendar(System.Globalization.GregorianCalendarTypes.TransliteratedFrench);
                case 16:
                    return new System.Globalization.GregorianCalendar();
                default:
                    return new System.Globalization.GregorianCalendar();
            }
        }

        private bool IsHoliday(DayRenderEventArgs e)
        {
            SPRegionalSettings regionalSettings = SPContext.Current.Web.CurrentUser.RegionalSettings ?? SPContext.Current.Web.RegionalSettings;
            short workDays = regionalSettings.WorkDays;

            return ((short)Math.Pow(2, (double)e.Day.Date.DayOfWeek) & workDays) == 0
                || (!string.IsNullOrEmpty(HolidayDates) && HolidayDates.Split(',').Any(d => e.Day.Date.Equals(Convert.ToDateTime(d))));
        }

        protected override void RenderContents(HtmlTextWriter output)
        {
            base.RenderContents(output);
            Page.ClientScript.RegisterArrayDeclaration("CWPTooltips", tooltipArray);
            Page.ClientScript.RegisterStartupScript(GetType(), "CWPTooltipScript", string.Format(
@"function CWPShowTooltip(i) {{
    //var divTooltip = document.getElementById('{0}');
    //divTooltip.innerHTML = CWPTooltips[i];
    //divTooltip.style.left = event.clientX + document.body.scrollLeft - divTooltip.offsetParent.offsetParent;
    //divTooltip.style.top = event.clientY + document.body.scrollLeft - divTooltip.offsetParent.offsetParent + 10;
    //divTooltip.style.display = '';
    Tip(CWPTooltips[i], CLICKCLOSE, true, FOLLOWMOUSE, false, DURATION, 3000);
}}
var cwpTimeout;
function CWPDontHideTooltip() {{
    clearTimeout(cwpTimeout);
}}
function CWPHideTooltip() {{
    cwpTimeout = setTimeout(""document.getElementById('{0}').style.display='none'"", 2000);
}}
document.getElementById('{1}').removeAttribute('title');", divTooltip.ClientID, cal.ClientID), true);
            Page.ClientScript.RegisterStartupScript(GetType(),
                "InlineStyle", "<style type=\"text/css\">.asn-calendartitle {background:url(/_layouts/images/selbg.png) #ccebff repeat-x left top}</style>",
                false);
        }

        protected override void CreateChildControls()
        {
            base.CreateChildControls();

            if (!string.IsNullOrEmpty(titleForeColor))
            {
                cal.TitleStyle.ForeColor = Color.FromName(titleForeColor);
            }
            if (!string.IsNullOrEmpty(titleBackColor))
            {
                cal.TitleStyle.BackColor = Color.FromName(titleBackColor);
            }
            else
            {
                cal.TitleStyle.BackColor = Color.FromArgb(0xd6, 0xe8, 0xff);
            }

            cal.CssClass = "ms-quickLaunch";
            cal.TitleStyle.CssClass = "asn-calendartitle";
            cal.TitleStyle.BorderWidth = Unit.Pixel(0);
            cal.ID = "CWPCalendar";
            //cal.ToolTip = "";
            tooltipIndex = 0;
            Controls.Add(cal);
            cal.DataBind();

            divTooltip.Style["border"] = "1px solid black";
            divTooltip.Style["background-color"] = "#FFFFE1";
            divTooltip.Style["display"] = "none";
            divTooltip.Style["position"] = "absolute";
            divTooltip.ID = "CWPTooltip";
            divTooltip.Attributes["onmouseover"] = "CWPDontHideTooltip()";
            divTooltip.Attributes["onmouseout"] = "CWPHideTooltip()";
            Controls.Add(divTooltip);

            if (showAddNew && CalendarWeb.Lists[listName].DoesUserHavePermissions(SPBasePermissions.AddListItems))
            {
                HtmlGenericControl div = new HtmlGenericControl("div");
                div.Attributes["class"] = "ms-addnew";
                div.Style["padding-top"] = "3px";
                div.Style["float"] = "left";

                HtmlImage img = new HtmlImage();
                img.Src = "/_layouts/images/rect.gif";
                div.Controls.Add(img);
                lnkAddNewItem.Style["padding-left"] = "5px";
                lnkAddNewItem.Text = "Add new event";
                lnkAddNewItem.CssClass = "ms-addnew";
                if (!string.IsNullOrEmpty(WebId) && !string.IsNullOrEmpty(listName))
                {
                    SPList list = CalendarWeb.Lists[listName];
                    lnkAddNewItem.NavigateUrl = list.DefaultNewFormUrl;
                    lnkAddNewItem.Attributes["onclick"] = "javascript:NewItem2(event, '" + list.DefaultNewFormUrl + "');javascript:return false;";
                }
                div.Controls.Add(lnkAddNewItem);
                Controls.Add(div);
            }

            if (showExport)
            {
                HtmlGenericControl div = new HtmlGenericControl("div");
                div.Attributes["class"] = "ms-addnew";
                div.Style["padding-top"] = "3px";
                div.Style["padding-left"] = "5px";
                div.Style["float"] = "left";
                HtmlImage img = new HtmlImage();
                img.Src = "/_layouts/images/rect.gif";
                div.Controls.Add(img);
                lnkExport.Style["padding-left"] = "5px";
                lnkExport.Text = "Export iCal";
                lnkExport.CssClass = "ms-addnew";
                if (!string.IsNullOrEmpty(listName))
                {
                    lnkExport.Click += new EventHandler(lnkExport_Click);
                }
                div.Controls.Add(lnkExport);
                Controls.Add(div);
            }

            lblExceptions.Text = string.Empty;
            Controls.Add(lblExceptions);
        }

        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);

            if (!Page.ClientScript.IsClientScriptIncludeRegistered(WZ_SCRIPT_KEY))
            {
                Page.ClientScript.RegisterClientScriptInclude(WZ_SCRIPT_KEY, "/Style Library/ASN/wz_tooltip.js");
            }
        }

        void lnkExport_Click(object sender, EventArgs e)
        {
            // TODO refactor the vCalendar class, some class properties for timezone information are not available in sandboxed solutions
            //SPList list = CalendarWeb.Lists[listName];
            //VCalendar.ExportVCalendar(list, Page);
        }

        public override EditorPartCollection CreateEditorParts()
        {
            return new EditorPartCollection(base.CreateEditorParts(),
                new[] {
                    new CalendarToolpart()
                });
        }
    }
}
